Amazon EC2を(なるべく)使わずにシステムを構築してみる
こんにちは、せーのです。AWSは現在40以上のサービスがあり、なかなか把握しきれないことも多いかと思います。そこで今日は現在のサービスを組み合わせたシステム構築の一例をご紹介致します。
最もコストがかかるのはEC2
そもそもオンプレではなくクラウドサービスを選ぶ理由は安価で簡単にサーバーやストレージを調達でき、障害対策や電源管理等をAWS側が行ってくれるから、という方も多いかと思います。 ではAWSの各サービスでコストを抑える秘訣はなんなのでしょう。それは「EC2を使わないこと」です。例えばDBとしてmySqlを使いたいとします。時間あたりの単価を考えるとEC2の中にmySqlをインストールするよりもRDSでmySqlを立てたほうがお得です。また障害が起きてダウンした際にEC2は自分でフェールオーバー等の対策を打つ必要がありますが、RDSはmulti-AZの設定をしておくだけで後はAWSがいい感じに対策をしてくれます。それらの手間もコストとして考えるとAWSにてシステム構築を安く、堅牢に構築するポイントは「いかにEC2を使わずマネージド・サービスにて済ませるか」という部分にあります。
そこでこんなユースケースを考えてみました。
ユーザーより登録情報を入力する。入力された登録情報はサーバーに保管され、フローに従って管理部門に回される。管理部門は登録情報を見て承認し、追加情報と担当者名を入力する。承認されるとユーザーには承認された旨が通知される。承認後も登録情報は管理部門から検索することが可能。
こちらをAWS上にて実現するにはどんな構成がいいでしょう?
AWS構成図
こんな感じの構成を考えました。いくつかポイントや妥協、悩みがありますので一緒にご紹介します。
EC2の代わりにS3
まずユーザーが登録情報を入力する画面、こちらはS3からStatic Web Hosting(静的ウェブサイトホスティング)にて配信します。EC2でApache等を立てなくても静的画面、つまりHTMLであればS3からホスティングすることができますのでこちらを利用します。このStatic Web Hostingのポイントは「JavaScriptは使える」というところです。ただの静的HTMLであれば中々使い道が限られてきますが、JavaScriptが使えるのであればHTML5 + JavaScriptで大抵の画面は表現できます。Static Web HostingはS3の管理画面から簡単に設定できます。
詳しくはこちらも御覧ください。
AWSにおける静的コンテンツ配信パターンカタログ(アンチパターン含む)
認証はCognitoを使う
EC2をなるべく使わないようにするためには画面(ブラウザやアプリ)から直接AWSリソースを操作する必要があります。その認証にはCognitoを使うのが一番簡単です。CognitoはAWSの認証と外部認証を紐付けて管理してくれるサービスで、例えばFacebook、Twitter、Google+、Amazon等の認証サービスをそのまま使ってAWSリソースにアクセスすることができます。
アプリの登録をしてApp IDをセットしてIdentity Poolを作るだけなので設定は慣れてしまえば簡単です。 Cognitoの記事はDevelopers.ioでも結構出ているので参考にしてみてください。
WebブラウザからLambda Functionを叩く
Cognitoで認証しているおかげでWebブラウザからAWSリソースが使えるようになったのですが、データを直接DynamoDBに入れるのではなく、一旦Lambdaを叩き、LambdaからDynamoDBを叩くようにしています。これは使っているのがiOSやAndroidアプリではなくJavaScriptだから、という部分が大きいです。JavaScriptはソースが全てユーザーに見えることが前提となります。ですのでここで直接DynamoDBやSQSを叩くより、一旦Lambdaを経由したほうがカプセル化が出来てセキュリティ的に安心となります。Lambda Functionの中身はNode.jsや最近ではJavaが使えるようになりましたが、これはEC2上でやっても別に構わないわけです。ここは使用メモリ、リクエスト回数とのトレードオフになります。1024MB等の大きめのメモリ数で数千万PVくらいを推測しているのならばむしろt2.smallとかを立てたほうが安く済むパターンもあるかと思いますが、大体の場合はLambdaの方がコストは安く済むのではないでしょうか。
//データをLambdaに送信 function sendDatas(dataA, dataB, dataC, dataD, dataE, dataF){ var lambda = new AWS.Lambda(); var params = { FunctionName: 'sample-functionA', InvokeArgs: '{"dataA": "' + dataA + '", "dataB": "' + dataB + '", "dataC": "' + dataC + '", "dataD": "' + dataD + '", "dataE": "dataE", "dataF": "' + dataF + '"}' }; lambda.invokeAsync(params, function(err, data) { if (err){ $("#logs").html(logs + "<br>error occured about data sending to lambda. - " + err.stack); logs = $("#logs").html(); } else { $("#logs").html(logs + "<br>data sent to lambda. - " + data.Status); logs = $("#logs").html(); } }); }
SQSのデータをEC2でポーリングしてRDSにレプリケートする
上記の構成図を見てもお分かりのようにデータはDynamoDBに全て登録されています。なのにRDSにデータをレプリケートする理由は要件にある[管理部門は登録情報を見て承認し]を考慮したものです。DynamoDBはレスポンスの速さ等の長所がある一方複雑な条件での検索はSQLの使えるRDSの方が一日の長があります。ですのでここではメインのデータストアとしてはDynamoDBを、検索用兼バックアップ用としてRDSにデータをコピーすることにしました。 RDSはVPCの中にあるのでLambdaから直接叩くには動的なIPの穴あけなどトリッキーな操作が必要になります。それに比べてSQSをEC2でポーリングするのは安定感があるのと、もうすぐリリースされると言われている「DynamoDB Streams」に置き換える(DynamoDB StreamsはKinesisクライアントアプリ、つまりEC2で受け取れる)事を想定しているのでEC2を(泣く泣く)使いました。LambdaがVPC内で使えるようなアップデートを心待ちにしています。
#!/usr/bin/php <?php require_once("/home/ec2-user/aws-sdk-php/vendor/autoload.php"); $url = "https://sqs.us-east-1.amazonaws.com/123456789012/samplesqs"; use Aws\Sqs\SqsClient; $sqs = SqsClient::factory(array( 'region' => 'us-east-1' )); $response = $sqs->receiveMessage(array( 'QueueUrl' => $url, )); $body = $response['Messages'][0]['Body']; if(isset($body)) { error_log("Body: " . $body); } else { error_log("Empty"); return; } //parse $obj=json_decode($body, true); $rds_url="sample.c9aershb58pu.us-east-1.rds.amazonaws.com"; $user="test"; $pass="testtest"; $dbname="test"; $link = mysql_connect($rds_url,$user,$pass) or die("MySQLへの接続に失敗しました>。"); $sdb = mysql_select_db($dbname,$link) or die("データベースの選択に失敗しました。"); mysql_query('SET NAMES utf8', $link ); //var_dump($obj); $sql="INSERT INTO test(dataA,dataB,dataC,dataD,dataE,dataF) VALUES('"; $sql.= $obj['Item']['dataA']['S'] . "',"; $sql.= $obj['Item']['dataB']['N'] . ",'"; $sql.= $obj['Item']['dataC']['S'] . "','"; $sql.= $obj['Item']['dataD']['S'] . "','"; $sql.= $obj['Item']['dataE']['N'] . ",'"; $sql.= $obj['Item']['dataF']['S'] . "')"; } //var_dump($sql); //return; $result_flag = mysql_query($sql); if (!$result_flag) { die('クエリーが失敗しました。'.mysql_error()); } $receipt_handle = $response['Messages'][0]['ReceiptHandle']; if(isset($receipt_handle)) { $response = $sqs->deleteMessage(array( 'QueueUrl' => $url, 'ReceiptHandle' => $receipt_handle )); error_log("Delete: " . $receipt_handle); } if(!$response) { exit(1); } exit(0);
SNS mobile pushを使って直接モバイルに情報を送る
最後は情報を各端末に送る作業です。こちらはSNS mobile pushを使用することでモバイル(iPhone/iPadならAPNS, AndroidならGCM)に直接投げます。SNS mobile pushにつきましてはこの記事が非常にわかりやすいです。
[AWS] Amazon SNS の新機能「Mobile Push」を iOS で使ってみた
今回はSQSポーリングでEC2を使いましたので管理画面用WebとSNS mobile pushのAPIを叩くのについでに同じEC2を使ってしまいましたが本来はこの管理画面用WebとSNS mobile pushもそれぞれS3とLambdaでいけますので、実際はSQSのポーリング用のところだけEC2が必要となり、あとはEC2なしでこのシステムは構築出来ました!
まとめ
いかがでしたでしょうか。EC2を使わずにアーキテクチャを組み立てるポイントはLambdaとSNSになります。この2つのサービスを使いこなせれば今より費用はグッと抑えられるのではないでしょうか。 今回は意図的にEC2をなるべく使わない構成を考えましたが、これからクラウドが当たり前の世界になってくるとクラウドは「オンプレのサーバーの代わりをするもの」ではなく、クラウド独自のサービスを組み合わせて利用することでハードに関してのお守りは全てクラウドに任せて、より一層アプリやアーキテクチャに集中する「クラウドネイティブ」の時代がやってきます。 皆さんもこれからサービスをクラウドで組む、という時はまず「どれだけEC2を使わずにできるか」を考えると、クラウドネイティブな思考が身につきますよ!